package mt.network;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.LinkedBlockingQueue;

import mt.Order;
import mt.comm.ClientComm;
import mt.comm.ClientSideMessage;
import mt.comm.ServerMessage;

/**
 * Communication class for MicroTrader clients.
 * MicroTrader clients must communicate with MicroTrader server
 * through this class.
 * @author Group45
 *
 */
public class ClientCommNT implements ClientComm {
	public static final long KEEP_ALIVE = 1000;

	LinkedBlockingQueue<ClientSideMessage> receivedMessages = new LinkedBlockingQueue<ClientSideMessage>();

	private String host = "127.0.0.1";
	private String nickname = null;
	private ConnectionController controller;

	@Override
	public void connect(String host, String nickname)
			throws UnknownHostException, IOException {
		this.host = host;
		this.nickname = nickname;

		ClientSideMessage reply = sendMessage(new ServerMessage()
		.connect(nickname));

		if (reply != null && reply.getType() == null) {
			controller = new ConnectionController();
			(new Thread(controller)).start();
		}else if (reply != null && reply.getType() != ClientSideMessage.Type.ERROR){
			addMessage(reply);
		} else {
			throw new RuntimeException("invalid msg type received: "
					+ reply.getType());
		}
	}

	@Override
	public boolean isConnected() {
		return controller != null && !controller.isClosed();
	}

	@Override
	public void disconnect() {
		if(controller != null && !controller.isClosed){
			controller.close();
			sendMessage(new ServerMessage().disconnect(nickname));
			controller = null;
		}
	}

	@Override
	public synchronized ClientSideMessage getNextMessage() {
		try {
			if (receivedMessages.isEmpty()) {
				wait();
			}
			return receivedMessages.remove();
		} catch (InterruptedException e) {
			return null;
		}

	}

	@Override
	public boolean hasNextMessage() {
		return !receivedMessages.isEmpty();
	}

	@Override
	public void sendOrder(Order order) {
		sendMessage(new ServerMessage().newOrder(nickname, order));
	}

	private synchronized void addMessage(ClientSideMessage reply) {
		receivedMessages.add(reply);
		notifyAll();
	}

	private ClientSideMessage sendMessage(ServerMessage msg) {
		try {

			Socket s = new Socket(host, ServerCommNT.PORT);

			ObjectOutputStream output = new ObjectOutputStream(s.getOutputStream());
			ObjectInputStream input = new ObjectInputStream(s.getInputStream());

			output.writeObject(msg);
			output.flush();

			try {
				ClientSideMessage reply = (ClientSideMessage) input
						.readObject();

				input.close();
				output.close();
				s.close();

				return reply;
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				s.close();
				throw new RuntimeException();
			}
		} catch (IOException e) {
			disconnect();
		}
		throw new RuntimeException("Big fail");
	}

	private class ConnectionController implements Runnable {

		private boolean isClosed = false;

		@Override
		public void run() {
			try {
				while (!isClosed) {
					ClientSideMessage reply = sendMessage(new ServerMessage()
					.connect(nickname));

					if (reply != null){
						if(reply.getType() != null && reply.getType() != ClientSideMessage.Type.ERROR) {
							addMessage(reply);
						} else if(reply.getType() == null){
							Thread.sleep(KEEP_ALIVE);
						}else if(reply.getType() != ClientSideMessage.Type.ERROR){
							disconnect();
						}
					}else{
						throw new RuntimeException("");
					}

				}
			} catch (InterruptedException e) {
				throw new RuntimeException("Thread interrupted");
			}
		}

		public void close() {
			isClosed = true;
		}

		public boolean isClosed() {
			return isClosed;
		}

	}

}
